package com.compunit.composite.core.cx;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import com.compunit.composite.core.AbstractExporter;
import com.compunit.composite.core.cx.enumn.EActivationTag;
import com.compunit.composite.core.cx.enumn.EBindingTag;
import com.compunit.composite.core.cx.enumn.EClassTag;
import com.compunit.composite.core.cx.enumn.EComponentTag;
import com.compunit.composite.core.cx.enumn.EConsumerTag;
import com.compunit.composite.core.cx.enumn.EPMappingTag;
import com.compunit.composite.core.cx.enumn.EProviderTag;
import com.compunit.composite.core.cx.enumn.ERMappingTag;
import com.compunit.composite.core.cx.enumn.EUnitTag;
import com.compunit.composite.core.interfaces.cx.ICxBinding;
import com.compunit.composite.core.interfaces.cx.ICxExporter;
import com.compunit.composite.core.interfaces.cx.ICxMapping;
import com.compunit.composite.core.interfaces.cx.ICxUnit;
import com.compunit.composite.core.xml.XMLTag;

/**
 * <code>CxExporter</code> defines the implementation
 * of an utility unit that is responsible for exporting
 * the cached component data to the given URL address.
 *
 * @author Yi Wang (Neakor)
 * @version Creation date: 11-09-2009 11:18 EST
 * @version Modified date: 12-05-2009 14:20 EST
 */
public class CxExporter extends AbstractExporter implements ICxExporter {
	/**
	 * The <code>List</code> of <code>ICxMapping</code>
	 * for provided interfaces.
	 */
	private final List<ICxMapping> provided;
	/**
	 * The <code>List</code> of <code>ICxMapping</code>
	 * for required interfaces.
	 */
	private final List<ICxMapping> required;
	/**
	 * The <code>List</code> of <code>ICxBinding</code>.
	 */
	private final List<ICxBinding> bindings;
	/**
	 * The <code>String</code> component name.
	 */
	private String name;
	/**
	 * The <code>ICxUnit</code> array of activation.
	 */
	private ICxUnit[] activation;
	
	/**
	 * Constructor of <code>CxExporter</code>.
	 */
	public CxExporter() {
		this.provided = new ArrayList<ICxMapping>();
		this.required = new ArrayList<ICxMapping>();
		this.bindings = new ArrayList<ICxBinding>();
	}
	
	@Override
	protected void doExport(final File destination) throws URISyntaxException, IOException {
		final PrintWriter writer = new PrintWriter(destination);
		final String output = this.constructString();
		writer.println(output);
		writer.close();
	}

	/**
	 * Construct the cx formatted string representation
	 * of the current cached data.
	 * @return The <code>String</code> to export.
	 */
	private final String constructString() {
		final XMLTag root = new XMLTag(EComponentTag.Name.getString());
		root.addAttribute(EComponentTag.Key_Name.getString(), this.name);
		this.addActivation(root);
		this.addMappings(root, this.provided, EPMappingTag.Name.getString(), EPMappingTag.Key_Interfacename.getString());
		this.addMappings(root, this.required, ERMappingTag.Name.getString(), ERMappingTag.Key_Interfacename.getString());
		this.addBindings(root);
		return root.toExternal();
	}
	
	/**
	 * Add the activation definition to the cx XML.
	 * @param root The root component <code>XMLTag</code>.
	 */
	private final void addActivation(final XMLTag root) {
		if(this.activation == null || this.activation.length==0) {
			throw new IllegalArgumentException("No activation order available.");
		}
		final XMLTag tag = new XMLTag(EActivationTag.Name.getString());
		root.addChild(tag);
		final int length = this.activation.length;
		for(int i = 0; i < length; i++) {
			final ICxUnit unit = this.activation[i];
			final XMLTag unittag = new XMLTag(EUnitTag.Name.getString());
			final String classname = unit.getClassname();
			final String resoucename = unit.getResourcename();
			if(classname != null) unittag.addAttribute(EUnitTag.Key_Classname.getString(), classname);
			if(resoucename != null) unittag.addAttribute(EUnitTag.Key_Resourcename.getString(), resoucename);
			tag.addChild(unittag);
		}
	}
	
	/**
	 * Add all the mappings in the given mapping list
	 * to the component definition with given mapping
	 * tag name.
	 * @param root The root component <code>XMLTag</code>.
	 * @param mappings The <code>List</code> of all the
	 * <code>ICxMapping</code> to be added.
	 * @param tagname The <code>String</code> name of
	 * the mapping tag.
	 * @param key The <code>String</code> interface key.
	 */
	private final void addMappings(final XMLTag root, final List<ICxMapping> mappings, final String tagname, final String key) {
		for(final ICxMapping mapping : mappings) {
			final XMLTag mappingtag = new XMLTag(tagname);
			mappingtag.addAttribute(key, mapping.getInterface());
			for(final String classname : mapping.getClasses()) {
				final XMLTag ptag = new XMLTag(EClassTag.Name.getString());
				ptag.addAttribute(EClassTag.Key_Classname.getString(), classname);
				mappingtag.addChild(ptag);
			}
			root.addChild(mappingtag);
		}
	}
	
	/**
	 * Add all binding tags to the root tag.
	 * @param root The root component <code>XMLTag</code>.
	 */
	private final void addBindings(final XMLTag root) {
		for(final ICxBinding binding : this.bindings) {
			final XMLTag btag = new XMLTag(EBindingTag.Name.getString());
			final XMLTag ptag = new XMLTag(EProviderTag.Name.getString());
			ptag.addAttribute(EProviderTag.Key_Classname.getString(), binding.getProvider());
			final XMLTag ctag = new XMLTag(EConsumerTag.Name.getString());
			ctag.addAttribute(EConsumerTag.Key_Classname.getString(), binding.getConsumer());
			btag.addChild(ptag);
			btag.addChild(ctag);
			root.addChild(btag);
		}
	}
	
	@Override
	public void addBinding(final ICxBinding binding) {
		if(binding == null) return;
		if(this.bindings.contains(binding)) return;
		this.bindings.add(binding);
	}

	@Override
	public void addMapping(final ICxMapping mapping) {
		if(mapping == null) return;
		if(mapping.isProvided()) {
			if(this.provided.contains(mapping)) return;
			this.provided.add(mapping);
		} else  {
			if(this.required.contains(mapping)) return;
			this.required.add(mapping);
		}
	}

	@Override
	public void removeBinding(final ICxBinding binding) {
		if(binding == null) return;
		this.bindings.remove(binding);
	}

	@Override
	public void removeMapping(final ICxMapping mapping) {
		if(mapping == null) return;
		if(mapping.isProvided()) {
			this.provided.remove(mapping);
		} else {
			this.required.remove(mapping);
		}
	}

	@Override
	public boolean containsBinding(final ICxBinding binding) {
		if(binding == null) return false;
		return this.bindings.contains(binding);
	}

	@Override
	public boolean containsMapping(final ICxMapping mapping) {
		if(mapping == null) return false;
		if(mapping.isProvided()) return this.provided.contains(mapping);
		else return this.required.contains(mapping);
	}

	@Override
	public void setActivation(final ICxUnit[] units) {
		if(units == null) return;
		this.activation = units;
	}

	@Override
	public void setName(final String name) {
		if(name == null) return;
		this.name = name;
	}
	
	@Override
	public String getName() {
		return this.name;
	}

	@Override
	public void cleanup() {
		this.provided.clear();
		this.required.clear();
		this.bindings.clear();
		this.name = null;
		this.activation = null;
	}

	@Override
	public boolean isEmpty() {
		return (this.provided.isEmpty() && this.required.isEmpty() && this.bindings.isEmpty() && this.activation==null && this.name== null);
	}
}
